package net.sf.xoperator.xmpp;

import groovyjarjarasm.asm.tree.IntInsnNode;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;

import net.sf.xoperator.Constants;
import net.sf.xoperator.rdfxmpp.SPARQLQuery;
import net.sf.xoperator.rdfxmpp.SPARQLResult;

import org.apache.commons.httpclient.UsernamePasswordCredentials;
import org.apache.log4j.Logger;
import org.jivesoftware.smack.ChatManagerListener;
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.PacketListener;
import org.jivesoftware.smack.Roster;
import org.jivesoftware.smack.RosterEntry;
import org.jivesoftware.smack.RosterListener;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.filter.PacketFilter;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.provider.ProviderManager;
import org.jivesoftware.smackx.ServiceDiscoveryManager;

/**
 * is the interface to and from the jabber network. Can generate accounts, which
 * is still unsafe, as it uses the password from the main account, so it exposes
 * this password to the proxy accounts server maintainer
 * 
 * @author joerg
 * 
 */
public class XmppClient {

	{
		ProviderManager.getInstance().addIQProvider("query",
				Constants.SPARQL_XMPP_SPEC_URL, new SPARQLQuery.Provider());
		ProviderManager.getInstance().addIQProvider("sparql-result",
				Constants.SPARQL_XMPP_SPEC_URL, new SPARQLResult.Provider());
		Roster.setDefaultSubscriptionMode(Roster.SubscriptionMode.manual);
	}

	private XMPPConnection conn = null;

	private ServiceDiscoveryManager sdisc = null;

	private List<String> agentJids = null;

	private AgentListener rosterListener;

	
	
	AccountInfo accountInfo;

	private static final Logger log = Logger.getLogger(XmppClient.class);



	public XmppClient(AccountInfo info) {
		this.accountInfo = info;
	}

	public XmppClient login() throws XMPPException {
		connect();
		if (log.isDebugEnabled()) {
			this.conn.addPacketListener(new PacketListener() {
				public void processPacket(Packet packet) {
					log.debug("receiving that: " + packet.toXML());

				}
			}, new PacketFilter() {
				public boolean accept(Packet arg0) {
					return true;
				}
			});
			this.conn.addPacketWriterListener(new PacketListener() {
				public void processPacket(Packet packet) {
					log.debug("sending this: " + packet.toXML());

				}
			}, new PacketFilter() {
				public boolean accept(Packet arg0) {
					return true;
				}
			});
		}
		synchronized (this) {
			if(this.accountInfo.isStandalone()){
				Roster.setDefaultSubscriptionMode(Roster.SubscriptionMode.accept_all);
			}
			this.conn.login(this.accountInfo.getUsername().split("@")[0],
					this.accountInfo.getPassword(), this.accountInfo.getResource());
			
			if(this.accountInfo.isStandalone()){
				Roster.setDefaultSubscriptionMode(Roster.SubscriptionMode.manual);
			}
			
		}

		
		

		
		this.sdisc = ServiceDiscoveryManager.getInstanceFor(this.conn);

		return this;

	}

	private void connect() throws XMPPException {
		String server = this.accountInfo.getServer();
		String username = this.accountInfo.getUsername();
		int port = this.accountInfo.getPort() != null ? this.accountInfo
				.getPort() : Constants.DEFAULT_JABBER_PORT;
		if ((server == null || server.isEmpty()) && (username != null)) {
			server = username.split("@")[1];
		}
		ConnectionConfiguration conf = new ConnectionConfiguration(server,
				port, username.split("@")[1]);
		this.conn = new XMPPConnection(conf);
		this.conn.connect();
	}

	public void logoff() {
		if (this.conn != null) {
			this.conn.disconnect();
		}

	}

	public Roster getRoster() {
		return this.conn.getRoster();
	}

	public void query(IQ query, int timeout) {
		this.conn.sendPacket(query);
	}

	/**
	 * creates a new jabber account, uses some info from the main account, if
	 * nothin is specified in the proxy account
	 * 
	 * @param mainAccount
	 * @return
	 * @throws XMPPException
	 */
	public AccountInfo createAccount(AccountInfo mainAccount)
			throws XMPPException {

		if (this.accountInfo.getServer() == null
				|| this.accountInfo.getServer().isEmpty()) {
			this.accountInfo.setServer(Constants.DEFAULT_SERVER);
		}
		connect();
		org.jivesoftware.smack.AccountManager accman = this.conn
				.getAccountManager();
		if (accman.supportsAccountCreation()) {
			String usr = this.accountInfo.getUsername();
			String pwd = this.accountInfo.getPassword();

			// TODO this is a cheesy hack, do some good name generation!
			if (usr == null || usr.isEmpty()) {
				usr = mainAccount.getUsername().split("@")[0]
						+ Constants.STANDARD_PROXY_ACCOUNT_EXTENSION;
			}

			if (pwd == null || pwd.isEmpty()) {
				pwd = mainAccount.getPassword();
				// pwd = RandomStringUtils.random(12);
			}

			log.info("not Creating account: " + usr + ":" + pwd);
			// accman.createAccount(usr, pwd);
			this.accountInfo.setUsername(usr);
			this.accountInfo.setPassword(pwd);
			// creating new Account info from the just created account
			return this.accountInfo;

		} else {
			throw new Error(Constants.ERR_ACC_004);
		}
	}

	protected void registerChatListener(ChatManagerListener listener) {
		this.conn.getChatManager().addChatListener(listener);
	}

	public AccountInfo getAccountInfo() {
		return this.accountInfo;
	}

	public boolean isLoggedIn() {
		if (this.conn != null && this.conn.isAuthenticated()) {
			return true;
		} else {
			return false;
		}
	}

	@Override
	protected void finalize() throws Throwable {
		super.finalize();
		if (this.conn != null) {
			this.conn.disconnect();
		}

	}

	public boolean isContactSPARQLEnabled(String jid) {
		if (agentJids == null) {
			createJidCollector();
		}
		return agentJids.contains(jid);
	}

	private void createJidCollector() {
		agentJids = new Vector<String>();
		rosterListener = new AgentListener();
		conn.getRoster().addRosterListener(rosterListener);
		rosterListener.init();

	}

	public List<String> getSPARQLEnabledClients() {
		if (agentJids == null) {
			createJidCollector();
		}

		return Collections.unmodifiableList(agentJids);

	}

	public void setSPARQLEnabled(boolean enabled, PacketListener getListener,
			PacketFilter getFilter, PacketListener resListener,
			PacketFilter resFilter) {
		ServiceDiscoveryManager sdm = ServiceDiscoveryManager
				.getInstanceFor(conn);
		if (enabled) {
			sdm.addFeature(Constants.SPARQL_XMPP_SPEC_URL);

			conn.addPacketListener(getListener, getFilter);
			conn.addPacketListener(resListener, resFilter);

		} else {
			sdm.removeFeature(Constants.SPARQL_XMPP_SPEC_URL);
			conn.removePacketListener(getListener);
			conn.removePacketListener(resListener);
			conn.getRoster().removeRosterListener(rosterListener);
			agentJids = null;
		}

	}

	public XMPPConnection getConn() {
		return conn;
	}

	public void sendQuery(SPARQLQuery query) {

		conn.sendPacket(query);
		log.info("Sent query: " + query.getPacketID() + " "
				+ query.getChildElementXML());

	}

	public void respondToQuery(SPARQLQuery query, SPARQLResult result) {
		result.setTo(query.getFrom());
		result.setPacketID(query.getPacketID());
		result.setStoreName(query.getTo() + ":" + result.getStoreName());
		conn.sendPacket(result);
		log.info("Sending a result, with id:" + result.getPacketID()
				+ " containing: " + result.getResult());
		result.setSent(true);
	}

	private class AgentListener implements RosterListener {

		public void init() {
			for (RosterEntry entry : getRoster().getEntries()) {
				Iterator<Presence> ipresence = getRoster().getPresences(
						entry.getUser());

				while (ipresence.hasNext()) {
					Presence pres = ipresence.next();

					if (pres.isAvailable()
							&& isContactSPARQLEnabled(pres.getFrom())) {
						agentJids.add(pres.getFrom());
					}
				}

			}

		}

		public void entriesAdded(Collection<String> arg0) {
			// TODO Auto-generated method stub

		}

		public void entriesDeleted(Collection<String> arg0) {
			// TODO Auto-generated method stub

		}

		public void entriesUpdated(Collection<String> arg0) {
			// TODO Auto-generated method stub

		}

		public void presenceChanged(Presence presence) {
			if (presence.isAvailable()) {
				if (isContactSPARQLEnabled(presence.getFrom())) {
					agentJids.add(presence.getFrom());
				}
			} else if (agentJids.contains(presence.getFrom())) {
				agentJids.remove(presence.getFrom());
			}

		}

		private boolean isContactSPARQLEnabled(String jid) {

			try {
				if (ServiceDiscoveryManager.getInstanceFor(conn).discoverInfo(
						jid).containsFeature(Constants.SPARQL_XMPP_SPEC_URL)) {
					log.info("detected agent at: " + jid);
					return true;
				}
			} catch (XMPPException e) {
				log.error("Error querying for the feature set of " + jid, e);
				try {
					Thread.sleep(3000);
				} catch (InterruptedException e1) {
					log.error("Unable to sleep!");
				}
			}

			return false;
		}

	}
	
	
	public void allowRosterAccess(String jid, String nick){
		try {
			if(!getRoster().contains(jid)){
				getRoster().createEntry(jid, nick, null);
			}
			Presence pres = new Presence(Presence.Type.subscribed);
			pres.setTo(jid);
			conn.sendPacket(pres);
		} catch (XMPPException e) {
			log.error("Error adding to Roster: " ,e);
		}
		
	}
	
	

}
